package com.xlabs.client;

import java.util.List;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.xlabs.client.ChromelessPlayer.PlayerListener;
import com.xlabs.client.ChromelessPlayer.PlayerState;
import com.xlabs.client.DTO.QuestionDTO;
import com.xlabs.client.DTO.TransferDTO;
import com.xlabs.client.richtext.ImageButton;
import com.xlabs.client.richtext.PersistentImageButton;

/**
 * The Questions class.
 * @author: a.akhavan.b
 */
public class Questions extends Composite implements PlayerListener 
{
	
	/**
	 * Paging size.
	 */
	private static final int PAGE_SIZE = 10;

	/**
	 * Time Interval after which questions will be synced (in seconds).  
	 */
	protected static final double SYNC_INTERVAL = 10;
	
	/**
     * video widget with which these questions associate.
     */
    private ChromelessPlayer videoPlayer;

    /**
     * Conversation UI for questions.
     */
    private Conversation conversation;

	/**
	 * Questions scroll panel.
	 */
	private ScrollPanel questionsScroll;
	
	/**
	 * Paging panel.
	 */
	private HorizontalPanel pagingPanel = new HorizontalPanel(); 

	/**
	 * The outer panel.
	 */
    private VerticalPanel outerPanel;
    
    /**
     * Last question clicked.
     */
    private static int LastQuestionClicked = -1;

	/**
	 * Panel including controls for syncing questions with the lecture.
	 */
    private HorizontalPanel questionsSyncPanel;

    /**
     * First question's start time index. 
     */
    protected String firstQuestionStartTimeIndex;

	/**
	 * Last question's start time index.
	 */
    protected String nextPageQuestionStartTimeIndex;

	/**
	 * Button for navigating to next page of questions.
	 */
    private ImageButton nextPageQuestionsButton;

	/**
	 * Button for navigating to previous page of questions.
	 */
    private ImageButton prevPageQuestionsButton;
    
    /**
     * Timer for syncing questions with the lecture.
     */
    private Timer syncTimer;

	/**
	 * Question sync button.
	 */
    private PersistentImageButton syncButton;

	/**
	 * Last time in the video that questions got synced.
	 */
    protected double lastPlayTimeQuestionsSync;

	/**
	 * Whether questions are being synced with video.
	 */
    private boolean isQuestionSyncing;
    
    /**
     * First question's start time.
     */
    private Integer firstQuestionStartTime;

	/**
	 * last Question's start time.
	 */
    private Integer lastQuestionStartTime;

    /**
     * Initializes a new instance of the Questions class.
     * @param video video widget with which these questions associate.
     * @param conversation Conversation UI for questions.
     */
    public Questions(ChromelessPlayer video, Conversation conversation) 
    {
        this.videoPlayer = video;
        this.conversation = conversation;
        
        initializeQuestionsScrollPanel();
        initializePagingPanel();
        initializeQuestionsSyncPanel();
        
        
        this.outerPanel = new VerticalPanel();
        this.outerPanel.addStyleName("Group");
        this.outerPanel.add(this.questionsSyncPanel);
        this.outerPanel.add(this.questionsScroll);
        this.outerPanel.add(this.pagingPanel);
        
        this.initWidget(this.outerPanel);
    }

	/**
	 * Initializes the questions sync panel.
	 */
    private void initializeQuestionsSyncPanel() 
    {
    	questionsSyncPanel = new HorizontalPanel();
    	syncButton = new PersistentImageButton(
    			"final-sync-icon.jpg", 
    			"Show questions asked at the play time every few seconds");
    	
    	syncButton.setSize("20", "20");
    	syncButton.addClickHandler(new ClickHandler()
		{
			public void onClick(ClickEvent event)
			{
				if (syncButton.isPressed())
				{
					startQuestionSync();
				}
				else
				{
					stopQuestionSync();				
				}
			}
		});
    	
    	questionsSyncPanel.add(syncButton);
	}

	/**
	 * Initializes the questions' scroll panel.
	 */
	private void initializeQuestionsScrollPanel() 
	{
		questionsScroll = new ScrollPanel();
        questionsScroll.addStyleName("ScrollPanel");
        questionsScroll.setSize("600", "260");
	}

	/**
	 * Initializes paging panel.
	 */
	private void initializePagingPanel() {
        this.pagingPanel.setSize("600", "10");
        
        this.pagingPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_LEFT);
        prevPageQuestionsButton = new ImageButton("Previous-icon.png", "");
        prevPageQuestionsButton.setSize("20", "20");
        this.pagingPanel.add(prevPageQuestionsButton);
        
        this.pagingPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_RIGHT);
        nextPageQuestionsButton = new ImageButton("Next-icon.png", "");
        nextPageQuestionsButton.setSize("20", "20");
        this.pagingPanel.add(nextPageQuestionsButton);
        
        prevPageQuestionsButton.addClickHandler(new ClickHandler() 
        {
			public void onClick(ClickEvent event) 
			{
				queryPreviousPage();
			}
		});
        
        nextPageQuestionsButton.addClickHandler(new ClickHandler() 
        {
			public void onClick(ClickEvent event) 
			{
				Questions.this.queryNextPage();
			}
		});
	}

    /**
     * Loads all questions.
     */
    public void queryFirstPage() {
        CommentingServiceAsync service = (CommentingServiceAsync) GWT.create(CommentingService.class);
        
    	/**
    	 * Callback that processes a page of questions.
    	 */
        AsyncCallback<TransferDTO> getFirstQuestionPageCallback = new AsyncCallback<TransferDTO>() {

			public void onFailure(Throwable throwable) 
            {
            	//TODO Need to provide an element for the failure responses and share among components
            }

    		@SuppressWarnings("unchecked")
			public void onSuccess(TransferDTO result)
    		{
    			List<QuestionDTO> dtoList = (List<QuestionDTO>) result.getValue(TransferDTO.QUESTIONLIST_KEY);
    			
				if (dtoList.size() == 0)
				{
					// shouldn't ever happen! 
					return; 					
				}
    			
				boolean hasNext = (Boolean) result.getValue(TransferDTO.HAS_NEXT_QUESTIONPAGE_KEY);

				updateQuestionsIndices(result, dtoList);
				nextPageQuestionsButton.setVisible(hasNext);
				prevPageQuestionsButton.setVisible(false);
				updateQuestionsRange(dtoList);
    			updateQuestionsScroll(dtoList);
    		}
        };
        
		service.getPagedQuestionsWithStartTimeInInterval(
        		this.videoPlayer.getCurrentVideoId(), 
        		String.valueOf(0), 
        		String.valueOf(Integer.MAX_VALUE),
        		TransferDTO.RIGHT_DIRECTION,
        		Questions.PAGE_SIZE, 
        		getFirstQuestionPageCallback);
    }
    
	protected void startQuestionSync()
	{
		if (syncTimer == null)
		{
			syncTimer = new Timer()
			{
				@Override
				public void run()
				{
					if (Math.abs(videoPlayer.getTime() - lastPlayTimeQuestionsSync) >= Questions.SYNC_INTERVAL &&
							(videoPlayer.getTime() > lastQuestionStartTime || videoPlayer.getTime() < firstQuestionStartTime))
					{
						lastPlayTimeQuestionsSync = videoPlayer.getTime();
						syncQuestionsWithVideo();										
					}
				}
			};			
		}
		
		syncQuestionsWithVideo();
		syncTimer.scheduleRepeating(5000);
		isQuestionSyncing = true;
	}
	
	/**
	 * Queries for questions asked at the play time.
	 * Remarks: Current implementation issues to queries on the server, so usage of this function should be minimal.
	 */
	@SuppressWarnings("unchecked")
	private void syncQuestionsWithVideo()
	{
		CommentingServiceAsync service = (CommentingServiceAsync) GWT.create(CommentingService.class);
		
		AsyncCallback<TransferDTO> getQuestionsAtPlayTimeCallBack = new AsyncCallback<TransferDTO>() 
		{
            public void onFailure(Throwable throwable) 
            {
            	//TODO Need to provide an element for the failure responses and share among components
            }

			public void onSuccess(TransferDTO result)
			{
				List<QuestionDTO> dtoList = (List<QuestionDTO>) result.getValue(TransferDTO.QUESTIONLIST_KEY);
    			boolean hasNext = (Boolean) result.getValue(TransferDTO.HAS_NEXT_QUESTIONPAGE_KEY);
    			boolean hasPrev = (Boolean) result.getValue(TransferDTO.HAS_PREV_QUESTIONPAGE_KEY);

    			updateQuestionsIndices(result, dtoList);
				nextPageQuestionsButton.setVisible(hasNext);
				prevPageQuestionsButton.setVisible(hasPrev);
				updateQuestionsRange(dtoList);
				updateQuestionsScroll(dtoList);
			}
        };

        service.getPagedQuestionsWithStartTimeAroundTime(
        		videoPlayer.getCurrentVideoId(), 
        		(int) videoPlayer.getTime(), 
        		Questions.PAGE_SIZE, 
        		getQuestionsAtPlayTimeCallBack);
	}

	protected void stopQuestionSync()
	{
		syncTimer.cancel();
		isQuestionSyncing = false;
	}

	/**
     * Queries next page of questions.
     */
	protected void queryNextPage() {
		CommentingServiceAsync service = (CommentingServiceAsync) GWT.create(CommentingService.class);
		
	    AsyncCallback<TransferDTO> getQuestionNextPageCallback = new AsyncCallback<TransferDTO>() 
	    {
	        public void onFailure(Throwable throwable) 
	        {
	        	//TODO Need to provide an element for the failure responses and share among components
	        }

			@SuppressWarnings("unchecked")
			public void onSuccess(TransferDTO result)
			{
				List<QuestionDTO> dtoList = (List<QuestionDTO>) result.getValue(TransferDTO.QUESTIONLIST_KEY);
				
				if (dtoList.size() == 0)
				{
					// shouldn't ever happen! 
					return; 					
				}
				
				boolean hasNext = (Boolean) result.getValue(TransferDTO.HAS_NEXT_QUESTIONPAGE_KEY);
				updateQuestionsIndices(result, dtoList);
		
				nextPageQuestionsButton.setVisible(hasNext);
				prevPageQuestionsButton.setVisible(true);
				updateQuestionsRange(dtoList);
				updateQuestionsScroll(dtoList);
			}
	    };
		
        service.getPagedQuestionsWithStartTimeInInterval(
        		this.videoPlayer.getCurrentVideoId(), 
        		nextPageQuestionStartTimeIndex, 
        		String.valueOf(Integer.MAX_VALUE), 
        		TransferDTO.RIGHT_DIRECTION, 
        		Questions.PAGE_SIZE, 
        		getQuestionNextPageCallback);
	}

	/**
	 * Queries previous page of questions.
	 */
	protected void queryPreviousPage() {
		
		CommentingServiceAsync service = (CommentingServiceAsync) GWT.create(CommentingService.class);
		
	    AsyncCallback<TransferDTO> getQuestionPrevPageCallback = new AsyncCallback<TransferDTO>() {
	        public void onFailure(Throwable throwable) 
	        {
	        	//TODO Need to provide an element for the failure responses and share among components
	        }

			@SuppressWarnings("unchecked")
			public void onSuccess(TransferDTO result)
			{
				List<QuestionDTO> dtoList = (List<QuestionDTO>) result.getValue(TransferDTO.QUESTIONLIST_KEY);
				
				if (dtoList.size() == 0)
				{
					// shouldn't ever happen! 
					return; 					
				}
				
				boolean hasPrev = (Boolean) result.getValue(TransferDTO.HAS_PREV_QUESTIONPAGE_KEY);
				
				updateQuestionsIndices(result, dtoList);
				nextPageQuestionsButton.setVisible(true);
				prevPageQuestionsButton.setVisible(hasPrev);
				updateQuestionsRange(dtoList);
				updateQuestionsScroll(dtoList);
			}
	    };

        service.getPagedQuestionsWithStartTimeInInterval(
        		this.videoPlayer.getCurrentVideoId(), 
        		String.valueOf(Integer.MIN_VALUE), 
        		firstQuestionStartTimeIndex,
        		TransferDTO.LEFT_DIRECTION, 
        		Questions.PAGE_SIZE, 
        		getQuestionPrevPageCallback);	
	}

    /**
     * Updates the questions view with the given questions.
     * @param questions input questions.
     */
    public void updateQuestionsScroll(List<QuestionDTO> questions) 
    {
        Grid allQuestions = new Grid(questions.size() + 1, 4);
		allQuestions.getColumnFormatter().setWidth(0, "100");
		allQuestions.getColumnFormatter().setWidth(1, "1000");
		allQuestions.setCellPadding(2);
		
		this.questionsScroll.setWidget(allQuestions);
		
		allQuestions.setWidget(0, 0, new Label("Time"));
		allQuestions.setWidget(0, 1, new Label("Comment"));
		allQuestions.setWidget(0, 2, new Label("User"));
		allQuestions.setWidget(0, 3, new Label("Action"));
		allQuestions.getRowFormatter().addStyleName(0, "gridHeader");
		
		int index = 1;
		
		for (QuestionDTO question : questions)
		{
		    Label timeCell = new Label("" + Questions.formatTime(question.getStartTime()));
		    timeCell.addClickHandler(new CommentTimeClickHandler(question.getStartTime()));
		    timeCell.addStyleName("SimpleTextLink");
		    allQuestions.setWidget(index, 0, timeCell);
		    
		   //TODO move this initialization to QuestionPanel
		    Label questionCell = new Label(question.getQuestionText());
		    questionCell.addStyleName("SimpleTextLink");
		    questionCell.addClickListener(new QuestionListener(question, this.conversation, index, allQuestions));
		    QuestionPanel qpanel = new QuestionPanel(question.getEncodedKey(),questionCell);
		    
		    allQuestions.setWidget(index, 1, qpanel);
		
		    Label userCell = new Label(question.getCreatedBy());
		    allQuestions.setWidget(index, 2, userCell);
		
		    ImageButton replyButton = new ImageButton("Email-Reply-icon.png", "");
		    replyButton.setSize("20", "20");
		    replyButton.addClickListener(new ReplyListener(index, allQuestions));
		    allQuestions.setWidget(index, 3, replyButton);
		
		    index = index + 1;
		}
    }

	/**
	 * @param questions
	 */
	private void updateQuestionsRange(List<QuestionDTO> questions)
	{
		firstQuestionStartTime = questions.get(0).getStartTime();
		lastQuestionStartTime = questions.get(0).getStartTime();
	}
	
	/**
	 * Updates previous page and next page questions indices.
	 * @param result result from the server.
	 * @param dtoList List of question dto's.
	 */
	private void updateQuestionsIndices(TransferDTO result,	List<QuestionDTO> dtoList)
	{
		nextPageQuestionStartTimeIndex = (String) result.getValue(TransferDTO.NEXT_PAGE_QUESTION_START_TIME_INDEX_KEY);
		firstQuestionStartTimeIndex = dtoList.get(0).getStartTimeIndex();
	}
    
    /**
     * Formats time given in seconds to HH:MM:SS
     * @param timeInSeconds time in seconds. 
     * @return time in HH:MM:SS
     */
    private static String formatTime(Integer timeInSeconds) {
    	int hours = timeInSeconds / 3600;
    	int remainder = timeInSeconds % 3600;
    	int minutes = remainder / 60;
    	int seconds = remainder % 60;
    	
    	return (hours + ":") + (minutes + ":") + seconds;
	}

    private class QuestionPanel extends VerticalPanel{
        /**
         * Question key.
         */
        private String questionKey;
        
        /**
         * Question label
         */
        private Label questionLabel;
        /**
         * Reply box which will be created
         */
        VerticalPanel replyBox; 
        
        public QuestionPanel(final String questionKey,final Label questionLabel){
            super();
            this.questionKey = questionKey;
            this.questionLabel = questionLabel;
            this.add(this.questionLabel); 
        }
        
        public void showReplyPanel(){
            //TODO how to check if the replyBox is already set or not
            if(replyBox == null){
                initializeReplyBox();
            } 
            replyBox.setVisible(true);
            /*Set focus to the test area*/
            TextArea replyArea = (TextArea) replyBox.getWidget(0);
            replyArea.setFocus(true);
        }
        
        public void hideReplyPanel(){
        	if (replyBox != null)
        	{
        		replyBox.setVisible(false);
        	}
        }
        
        private void initializeReplyBox(){
            //Label commentLAbel = (Label) this.commentsGrid.getWidget(index, 1);
            
        
            replyBox = new VerticalPanel();
            //replyBox.add(commentLAbel);

            final TextArea replyArea = new TextArea();
            replyArea.setSize("300px", "100px");
            //make area invisible if ESC is pressed
            replyArea.addKeyPressHandler(
                    new KeyPressHandler() {
                        public void onKeyPress(KeyPressEvent event) {
                          if (KeyCodes.KEY_ESCAPE==event.getCharCode()) {
                              //replyBox.setVisible(false);
                              replyBox.setVisible(false);                              
                          }
                        }
                    }
                  );
            
            
            Button submitButton = new Button("Post Reply", new ClickHandler() 
            {
                public void onClick(final ClickEvent event) 
                {
                    CommentingServiceAsync myService = (CommentingServiceAsync) GWT.create(CommentingService.class);
                    final Button source = (Button) event.getSource();

                    myService.postReply(replyArea.getText(), questionKey, new AsyncCallback<Object>()
                    		{

		                        public void onFailure(Throwable throwable) 
		                        {
		                            replyArea.setText("Failed to post Reply : " + replyArea.getText() + " Because of " + throwable);
		                        }
		
		                        public void onSuccess(Object o) {
		                            replyArea.setText("Posted Reply : " + replyArea.getText());
		                            replyArea.setReadOnly(true);
		                            source.setEnabled(false);
		                        }
                    		});
                }
            });
            
            replyBox.add(replyArea);
            replyBox.add(submitButton);
            this.add(replyBox);
            
            //this.commentsGrid.setWidget(index, 1, replyBox);
        }
    }
    
	/**
     * CommentListener class.
     */
    private class QuestionListener implements ClickListener {
        /**
         * Corresponding question dto.
         */
        private QuestionDTO questionDTO;

        /**
         * conversation widget to be updated.
         */
        private Conversation conversation;

        /**
         * Index of question cell on questionGrid.
         */
        private int index;

        /**
         * Question grid source.
         */
        private Grid questionGrid;

        /**
         * Constructor.
         * @param questionDTO questionDTO.
         * @param conversation conversation widget to be updated.
         * @param index   Index of question cell on questionGrid.
         * @param questionGrid  Question grid source.
         */
        private QuestionListener(QuestionDTO questionDTO, Conversation conversation, int index, Grid questionGrid) {

            this.questionDTO = questionDTO;
            this.conversation = conversation;
            this.index = index;
            this.questionGrid = questionGrid;
        }

        /**
         * onClick.
         * @param widget widget that got clicked on.
         */
        public void onClick(Widget widget) {
            this.conversation.showConversation(questionDTO);
            
            if (Questions.LastQuestionClicked != -1)
            {
                this.questionGrid.getRowFormatter().removeStyleName(Questions.LastQuestionClicked, "ClickedQuestion");
            }
            
            this.questionGrid.getRowFormatter().addStyleName(index, "ClickedQuestion");
            
            Questions.LastQuestionClicked = index;
        }
    }

    /**
     * ReplyListener class.
     */
    private class ReplyListener implements ClickListener {

        /**
         * index of reply.
         */
        private int index;

        /**
         * Comments grid.
         */
        private Grid commentsGrid;

        /**
         * Initializes an instance of ReplyListener.
         * @param index index of row clicked.
         * @param commentsGrid The comments grid.
         * @param questionKey key of the question.
         */
        public ReplyListener(int index, Grid commentsGrid) 
        {
            this.index = index;
            this.commentsGrid = commentsGrid;
        }

        /**
         * Onclick.
         * @param widget widget.
         */
        public void onClick(Widget widget) 
        {
            int rows = commentsGrid.getRowCount();
            
            for (int i=1; i<rows; i++)
            { 
            	//first row is the header
                QuestionPanel qpanel = (QuestionPanel)this.commentsGrid.getWidget(i, 1);
                if (i==index)
                {
                    qpanel.showReplyPanel();
                }
                else //hide everyone else
                {
                    qpanel.hideReplyPanel();
                }
            }
        }
    }

    /**
     * CommentListener class.
     */
    private class CommentTimeClickHandler implements ClickHandler 
    {

        /**
         * Time of the comment.
         */
        private double time;

        public CommentTimeClickHandler(double time) {
            this.time = time;
        }
        
		public void onClick(ClickEvent event)
		{
            videoPlayer.seekTime(time);
			
		}
    }

	public void onReady()
	{
		// TODO Auto-generated method stub
		
	}

	/**
	 * See {@link PlayerListener}{@link #onStateChange(PlayerState)}
	 */
	public void onStateChange(PlayerState state)
	{
		switch (state) 
		{
			case PLAYING:
				if (isQuestionSyncing)
				{
					syncQuestionsWithVideo();
				}
		}
	}
}
